
import it.sauronsoftware.junique.AlreadyLockedException;
import it.sauronsoftware.junique.JUnique;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Properties;
import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
import org.apache.xmlrpc.server.PropertyHandlerMapping;
import org.apache.xmlrpc.server.XmlRpcServer;
import org.apache.xmlrpc.server.XmlRpcServerConfigImpl;
import org.apache.xmlrpc.webserver.WebServer;

public class Main {

	// Настройки сервера
	private static int port;
	private static String login;
	private static String password;
	private static String moodleUrl;
	private static ArrayList<Thread> testingThreads;
	private static boolean debugMode;

	private static void loadConfig() {
		Properties prop = new Properties();
		try {
			prop.load(new FileInputStream("config.properties"));

			port = Integer.parseInt(prop.getProperty("port"));
			login = prop.getProperty("login");
			password = prop.getProperty("password");
			moodleUrl = prop.getProperty("moodleUrl");
			debugMode = prop.getProperty("debug").equals("1");
			TestAttemptThread.loadConfig();
		} catch (IOException ex) {
			ex.printStackTrace();
			Log.log(Main.class, "No settings file found, exit", Log.ERROR);
			System.exit(0);
		}
	}

	/**
	 * Запуск XML-RPC сервера тестирования программ студентов
	 *
	 * @param args аргументы командной строки
	 */
	public static void main(String[] args) {
		checkSingleServer();
		loadConfig();

		testingThreads = new ArrayList<>();

		WebServer server = new WebServer(port);
		XmlRpcServer xmlRpcServer = server.getXmlRpcServer();
		PropertyHandlerMapping phm = new PropertyHandlerMapping();

		try {
			phm.addHandler("TestServer", Main.class);
		} catch (XmlRpcException e) {
			e.printStackTrace();
		}

		xmlRpcServer.setHandlerMapping(phm);
		XmlRpcServerConfigImpl serverConfig = (XmlRpcServerConfigImpl) xmlRpcServer.getConfig();
		serverConfig.setEnabledForExtensions(true);
		serverConfig.setContentLengthOptional(false);

		try {
			server.start();
			Log.log(Main.class, "XML-RPC Server started at port " + port, Log.OK);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Принимает файлы попытки через XML-RPC и создает их
	 *
	 * @param loginHash хэш от логина
	 * @param passwordHash хэш от пароля
	 * @param attemptID ID попытки
	 * @param m список файлов с названием и содержимым
	 * @return String статус заявки
	 */
	public String getAttemptFiles(String loginHash, String passwordHash, Integer taskID, Integer attemptID, HashMap<String, String> m, HashMap<String, String> testdirs) {
		if (checkAuth(loginHash, passwordHash)) {
			Log.log(this.getClass(), "Got attempt " + attemptID, Log.OK);
			Thread t = new Thread(new TestAttemptThread(taskID, attemptID, m, testdirs));
			Main.testingThreads.add(t);
			t.start();			
			return "200 OK";
		} else {
			Log.log(this.getClass(), "Unauthorized access", Log.WARNING);
			return "401 Unauthorized";
		}
	}

	/**
	 * Проверка логина и пароля, переданных через параметры с хранимыми в настройках
	 *
	 * @param loginHash хэш логина
	 * @param passwordHash хэш пароля
	 * @return boolean
	 */
	private static boolean checkAuth(String loginHash, String passwordHash) {
		if (loginHash.equals(StringUtils.md5(login)) && passwordHash.equals(StringUtils.md5(password))) {
			return true;
		} else {
			return false;
		}
	}

	public static void notifyCompileStarted(Integer attemptID) {
		XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
		try {
			config.setServerURL(new URL(moodleUrl));
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
		XmlRpcClient client = getXmlRpcClient();
		client.setConfig(config);
		Object[] params = new Object[]{StringUtils.md5(login), StringUtils.md5(password), attemptID};
		try {
			Object result = client.execute("notify_compile_started", params);
			if (result.toString().contains("200")) {
				Log.log(Main.class, "Notify compile started completed", Log.OK);
			}
			if (result.toString().contains("401")) {
				Log.log(Main.class, "Notify compile started failed, server responded 401", Log.ERROR);
			}
		} catch (XmlRpcException e) {
			Log.log(Main.class, "Notify compile started failed", Log.ERROR);
			e.printStackTrace();
		}
	}

	/**
	 * Послать в poasassignment xmlprc сообщение о неудавшейся компиляции
	 *
	 * @param attemptID id попытки
	 * @param compileMessage сообщение компилятора
	 */
	public static void notifyCompiled(Integer attemptID, String compileMessage) {
		Main.notifyCompiled(attemptID, false, compileMessage);
	}

	/**
	 * Послать в poasassignment xmlprc уведомление, прошла ли компиляция программы
	 *
	 * @param attemptID id попытки
	 * @param compiled флаг, была ли программа скомпилирована
	 */
	public static void notifyCompiled(Integer attemptID, boolean compiled) {
		Main.notifyCompiled(attemptID, compiled, "");
	}

	/**
	 * Послать в poasassignment xmlprc уведомление, прошла ли компиляция программы
	 *
	 * @param attemptID id попытки
	 * @param compiled флаг, была ли программа скомпилирована
	 * @param compileMessage сообщение компилятора
	 */
	public static void notifyCompiled(Integer attemptID, boolean compiled, String compileMessage) {
		XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
		try {
			config.setServerURL(new URL(moodleUrl));
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
		XmlRpcClient client = getXmlRpcClient();
		client.setConfig(config);
		Object[] params = new Object[]{StringUtils.md5(login), StringUtils.md5(password), attemptID, compiled, compileMessage};
		try {
			Object result = client.execute("notify_compiled", params);
			if (result.toString().contains("200")) {
				Log.log(Main.class, "Notify compiled completed", Log.OK);
			}
			if (result.toString().contains("401")) {
				Log.log(Main.class, "Notify compiled failed, server responded 401", Log.ERROR);
			}
		} catch (XmlRpcException e) {
			Log.log(Main.class, "Notify compiled failed", Log.ERROR);
			e.printStackTrace();
		}
	}

	/**
	 * Послать в poasassignment xmlprc уведомление о начале тестирования
	 *
	 * @param attemptID id попытки
	 */
	public static void notifyTestStarted(Integer attemptID) {
		XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
		try {
			config.setServerURL(new URL(moodleUrl));
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
		XmlRpcClient client = getXmlRpcClient();
		client.setConfig(config);
		Object[] params = new Object[]{StringUtils.md5(login), StringUtils.md5(password), attemptID};
		try {
			Object result = client.execute("notify_test_started", params);
			if (result.toString().contains("200")) {
				Log.log(Main.class, "Notify test started completed", Log.OK);
			}
			if (result.toString().contains("401")) {
				Log.log(Main.class, "Notify test started failed, server responded 401", Log.ERROR);
			}
		} catch (XmlRpcException e) {
			Log.log(Main.class, "Notify test started failed", Log.ERROR);
			e.printStackTrace();
		}
	}

	public static void notifyTested(Integer attemptID, String test, String testIn, String testOut, String studentOut, Integer tested) {
		// Уведомить сервер, что программа была успешно скомпилирована / не удалось скомилировать программу
		XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
		try {
			config.setServerURL(new URL(moodleUrl));
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
		XmlRpcClient client = getXmlRpcClient();
		client.setConfig(config);
		Object[] params = new Object[]{StringUtils.md5(login), StringUtils.md5(password), attemptID, test, testIn, testOut, studentOut, tested};
		try {
			Object result = client.execute("notify_tested", params);
			if (result.toString().contains("200")) {
				Log.log(Main.class, "notifyTested completed", Log.OK);
			}
			if (result.toString().contains("401")) {
				Log.log(Main.class, "notifyTested failed, server responded 401", Log.ERROR);
			}
		} catch (XmlRpcException e) {
			Log.log(Main.class, "NotifyTested failed", Log.ERROR);
			e.printStackTrace();
		}
	}

	/**
	 * Уведомить сервер о количестве найденных тестовых файлов
	 *
	 * @param attemptID ID попытки
	 * @param testfound количество найденных тестовых файлов
	 */
	public static void notifyTestfilesFound(Integer attemptID, int testfound) {
		XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
		try {
			config.setServerURL(new URL(moodleUrl));
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
		XmlRpcClient client = getXmlRpcClient();
		client.setConfig(config);
		Object[] params = new Object[]{StringUtils.md5(login), StringUtils.md5(password), attemptID, testfound};
		try {
			Object result = client.execute("notify_testfiles_found", params);
			if (result.toString().contains("200")) {
				Log.log(Main.class, "notifyTestfilesFound completed", Log.OK);
			}
			if (result.toString().contains("401")) {
				Log.log(Main.class, "notifyTestfilesFound failed, server responded 401", Log.ERROR);
			}
		} catch (XmlRpcException e) {
			Log.log(Main.class, "NotifyTested failed", Log.ERROR);
			e.printStackTrace();
		}
	}

	/**
	 * Проверить, что в системе не запущен другой сервер автотестирования
	 */
	public static void checkSingleServer() {
		String appId = "remote-autotester";
		boolean alreadyRunning;
		try {
			JUnique.acquireLock(appId);
			alreadyRunning = false;
		} catch (AlreadyLockedException e) {
			alreadyRunning = true;
		}
		if (alreadyRunning) {
			Log.log(Main.class, "Remote autotester is already running!, exit", Log.ERROR);
			System.exit(0);
		}
	}
	
	public static XmlRpcClient getXmlRpcClient() {
		if (debugMode) {
			return new DebugXmlRpcClient();
		}
		else {
			return new XmlRpcClient();
		}
	}
}
